package org.harsh.transmission.common.codec;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import com.ruoyi.common.config.RuoYiConfig;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import org.harsh.common.Config;
import org.harsh.common.Constants;
import org.harsh.common.Util;
import org.harsh.dispatch.CommVars;
import org.harsh.transmission.common.CustomFile;
import org.harsh.transmission.common.NodeItem;
import org.harsh.transmission.common.NodeQueue;
import org.harsh.transmission.domain.TransMsg;
import org.harsh.transmission.domain.TransReceiver;
import org.harsh.transmission.domain.TransRecvmsg;
import org.harsh.transmission.domain.TransTask;
import org.harsh.transmission.server.NodeServer;
import org.harsh.transmission.service.ITransMsgService;
import org.harsh.transmission.service.ITransReceiverService;
import org.harsh.transmission.service.ITransRecvmsgService;
import org.harsh.transmission.service.ITransTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Component
@ChannelHandler.Sharable
public class FileMsgCodec  extends MessageToMessageCodec<CustomFile, File> {
    @Autowired
    private ITransMsgService transMsgService;
    @Autowired
    private ITransTaskService transTaskService;
    @Autowired
    private ITransRecvmsgService transRecvmsgService;
    @Autowired
    private ITransReceiverService transReceiverService;

    /**
     * 从File类型进行编码
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, File file, List<Object> list) throws Exception {
        long startTime = System.currentTimeMillis();
        CustomFile customFile = new CustomFile(file);
        ctx.channel().writeAndFlush(customFile);
        log.info("Send file {}:{} to peer, file length : {}, time at {}", customFile.getMsgId(), customFile.getFileName(), customFile.getFileSize(), startTime);

        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
        int byteRead;
        long startPos = customFile.getStartpos();
        byte[] tmpData = new byte[Config.fileChunkSize];
        long length = (int) file.length() / Config.fileChunkSize;
        for (int i = 0; i < length+1; i++) {
            randomAccessFile.seek(startPos);
            if((byteRead = randomAccessFile.read(tmpData)) != -1) {
                if(byteRead < Config.fileChunkSize) {
                    tmpData = Arrays.copyOf(tmpData, byteRead);
                }
                CustomFile customFileData = new CustomFile(Constants.MessageType.TRANSFILEDATA.getCode(), customFile.getMsgId(), startPos, tmpData);
                ctx.writeAndFlush(customFileData);

            }
            startPos += byteRead;
        }
        randomAccessFile.close();
        CustomFile customFileOver = new CustomFile(Constants.MessageType.TRANSFILEOVER.getCode(), customFile.getMsgId());
        ctx.writeAndFlush(customFileOver);
        long overTime = System.currentTimeMillis();
        long costTime = (overTime - startTime);
        double speed = customFile.getFileSize() / (costTime * 1024 / 1000 );
        log.info("Send file data over to peer, over time: {}, cost Time {} ms, speed {} kB/s", overTime, costTime, speed);
    }

    /**
     * 解码
     *
     */
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, CustomFile customFile, List<Object> list) {
        if(customFile.getMsgType() == Constants.MessageType.TRANSFILEHEAD.getCode()) {
            byte transType = customFile.getTransType();
            long msgId = customFile.getMsgId();
            long startTime = System.currentTimeMillis();
            log.info("Start received file id: {} , name: {} ,size: {}, startTime : {}", msgId, customFile.getFileName(), customFile.getFileSize(), startTime);
            String fileName = customFile.getFileName();
            String baseFilePath = RuoYiConfig.getUploadPath();
            File tmp = new File(baseFilePath ,  fileName);
            if(!tmp.exists()) {
                try {
                    tmp.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            RandomAccessFile file = null;
            try {
                file = new RandomAccessFile(tmp, "rw");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            Constants.transFile(msgId, file);

            TransMsg transMsg = new TransMsg();
            transMsg.setMsgType(1l);
            transMsg.setMsgId(msgId);
            transMsg.setMsgSize(customFile.getFileSize());
            transMsg.setFromNode((long) customFile.getFromNode());
            transMsg.setFromType((long) customFile.getFromType());
            transMsg.setFromId((long) customFile.getFromId());
            transMsg.setMsgMd5(new String(customFile.getFileMd5(), Config.DEFAULT_CHARSET));
            transMsg.setMsgData("/upload/" + fileName);
            transMsgService.insertTransMsg(transMsg);

            TransRecvmsg transRecvmsg = new TransRecvmsg();
            transRecvmsg.setMsgId(msgId);
            transRecvmsg.setTransType((long) transType);
            // 开始接收、正在接收、接收中断、接收成功、接收失败
            transRecvmsg.setRecvStatus(1l);
            transRecvmsg.setStartTime(DateUtil.date());
            transRecvmsgService.insertTransRecvmsg(transRecvmsg);

            TransReceiver transReceiver = new TransReceiver();
            transReceiver.setMsgId(msgId);
            if(customFile.getRecvId() == 0) {
                // 由 业务系统 发起
                transReceiver.setRecvType(0l);
                transReceiver.setRecvNode(NodeServer.localDept.getDeptId());
                transReceiver.setRecvId(0l);
            } else {
                // 由 用户 发起
                transReceiver.setRecvType(1l);
                transReceiver.setRecvNode(NodeServer.localDept.getDeptId());
                transReceiver.setRecvId((long) customFile.getRecvId());
            }
            transReceiverService.insertTransReceiver(transReceiver);
        } else if(customFile.getMsgType()  == Constants.MessageType.TRANSFILEDATA.getCode()) {
            long msgId = customFile.getMsgId();
            RandomAccessFile file = Constants.getTransFile(msgId);
            try {
                if(Arrays.equals(Util.byteArrMD5(customFile.getChunkData()), customFile.getChunkMd5())) {
                    // 当前的pointer 与 startpos一致， 校验正确
                    file.seek(customFile.getStartpos());
                    file.write(customFile.getChunkData());
                    CustomFile successFile = new CustomFile(Constants.MessageType.ACKTRANSFILE.getCode(),
                            msgId, CustomFile.RecvDataCode.SUCCESS, customFile.getStartpos(), customFile.getChunkData().length);
                    channelHandlerContext.writeAndFlush(successFile);
                    log.info("Recv File {}, file startpos:{}, chunkSize : {}.", customFile.getMsgId(), customFile.getStartpos(), customFile.getChunkData().length);
                } else {
                    // todo 要求分片重传
                    // MD5 校验失败，则重新传输
                    file.seek(customFile.getStartpos());
                    file.write(customFile.getChunkData());
                    CustomFile errorFile = new CustomFile(Constants.MessageType.ACKTRANSFILE.getCode(),
                            msgId, CustomFile.RecvDataCode.ERROR, customFile.getStartpos(), 0L);
                    channelHandlerContext.writeAndFlush(errorFile);
                    log.error("【error】Recv File {}, file startpos:{}.", customFile.getMsgId(), customFile.getStartpos());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }  else if(customFile.getMsgType() == Constants.MessageType.TRANSFILEOVER.getCode() ) {
            long msgId = customFile.getMsgId();
            //check file and close
            RandomAccessFile file = Constants.getTransFile(msgId);
            long overTime = System.currentTimeMillis();
            try {
                log.info("Receive File {} transover， file len:{}, over time {}." , customFile.getMsgId(), file.length(), overTime);
                Constants.transOverFile(msgId);
            } catch (IOException e) {
                e.printStackTrace();
            }

            transRecvmsgService.setTransSuccess(msgId);
            CustomFile successFile = null;
            try {
                successFile = new CustomFile(Constants.MessageType.ACKTRANSFILEOVER.getCode(), msgId);
            } catch (IOException e) {
                e.printStackTrace();
            }
            channelHandlerContext.writeAndFlush(successFile);
        }  else if (customFile.getMsgType() == Constants.MessageType.ACKTRANSFILE.getCode())  {
            // todo  更新状态
            long msgId = customFile.getMsgId();
            byte recvCode = customFile.getRecvCode();
            long recvSize = customFile.getRecvSize();
            if(recvCode == CustomFile.RecvDataCode.SUCCESS) {
                InetSocketAddress address = (InetSocketAddress)channelHandlerContext.channel().remoteAddress();
                if(address!=null) {
                    String peerAddr = address.getAddress().getHostAddress();
                    Long nodeId = CommVars.addr2Node.get(peerAddr);
                    TransTask transTask = new TransTask();
                    transTask.setRecvNode(nodeId);
                    transTask.setMsgId(msgId);
                    List<TransTask> transTaskList = transTaskService.selectTransTaskList(transTask);
                    if (transTaskList != null) {
                        transTask = transTaskList.get(0);
                    }
                    transTask.setTaskOffset(recvSize);
                    transTaskService.updateTransTask(transTask);
                    CommVars.updateTaskRecvOffset(transTask, recvSize);
                }
            } else if (recvCode == CustomFile.RecvDataCode.ERROR) {
                // 传输出错了，重新发起传输
                InetSocketAddress address = (InetSocketAddress)channelHandlerContext.channel().remoteAddress();
                if(address!=null) {
                    String peerAddr = address.getAddress().getHostAddress();
                    Long nodeId = CommVars.addr2Node.get(peerAddr);
                    TransTask transTask = new TransTask();
                    List<TransTask> transTaskList = transTaskService.selectTransTaskList(transTask);
                    if (transTaskList != null) {
                        transTask = transTaskList.get(0);
                    }
                    transTask.setTaskOffset(recvSize);
                    transTask.setTaskStatus(2l);
                    transTaskService.updateTransTask(transTask);
                    CommVars.loadedTask.remove(transTask);
                }
            }
        }
        else if(customFile.getMsgType() == Constants.MessageType.ACKTRANSFILEOVER.getCode()) {
            // 文件传输响应
            long msgId = customFile.getMsgId();
            InetSocketAddress address = (InetSocketAddress)channelHandlerContext.channel().remoteAddress();
            if(address!=null) {
                String peerAddr = address.getAddress().getHostAddress();
                Long nodeId = CommVars.addr2Node.get(peerAddr);
                transTaskService.setTransSuccess(msgId, nodeId);
                log.info("Received ackMsg from peer, msgId : {}", msgId);
                log.info("File {} : {} transover.", customFile.getMsgId(), customFile.getFileName());
            }
        }
    }

}
